{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Overview\n",
    "\n",
    "This tutorial shows you how the library interacts with custom mods. To do so we create a generic mod that runs code on the vehicle and game engine side.\n",
    "\n",
    "In this tutorial we differentiate between mod and lua module:\n",
    "\n",
    "mod - refers to a collection of files that change the behavior of the game (see the zip file)\n",
    "\n",
    "lua module - refers to a lua 'library' that has a global name containing a table \n",
    "\n",
    "\n",
    "## Setup\n",
    "\n",
    "These are the usual imports to create a simple scenario. As the point is to show the mod support, nothing is happening in the scenario. The mod only prints some text to the console, so take a look at the console or the log file you can find in \"~\\Documents\\BeamNG.tech\". Note that the console only flushes the last line to the log file if another console output line is generated.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import zipfile\n",
    "from pathlib import Path\n",
    "\n",
    "from beamngpy import BeamNGpy, Scenario, Vehicle, setup_logging\n",
    "from beamngpy.beamngcommon import ack"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2021-01-05 09:35:44,103 INFO     Started BeamNGpy logging.\n",
      "2021-01-05 09:35:44,104 DEBUG    Determined BeamNG.* binary to be: D:\\BeamNG\\game\\Bin64\\BeamNG.drive.x64.exe\n"
     ]
    }
   ],
   "source": [
    "setup_logging()\n",
    "beamng = BeamNGpy('localhost', 64256)\n",
    "scenario = Scenario('smallgrid', \n",
    "                    \"On how to use custom mods\")\n",
    "\n",
    "etk = Vehicle('ego_vehicle', model='etk800', licence='AI', extensions=[\"vehicleEngineCode\"])\n",
    "scenario.add_vehicle(etk, pos=(0,0,0), rot_quat=(0, 0, 1, 0))\n",
    "\n",
    "pickup = Vehicle('some_vehicle', model='pickup', licence='AI', extensions=[\"vehicleEngineCode\"])\n",
    "scenario.add_vehicle(pickup, pos=(5,0,0), rot_quat=(0, 0, 1, 0))\n",
    "\n",
    "scenario.make(beamng)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Creating a Simple Mod\n",
    "\n",
    "Mods can be installed by creating a zip directory in the \".../Documents/BeamNG.reasearch/mods/\" folder. They have to recreate the exact same file structure as you find in the game directory in \"BeamNG.tech.vX.X.X.X/lua\".\n",
    "\n",
    "What happens here is that the two lua files in the BeamNGpy directory are zipped into a mod."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2021-01-05 09:35:47,046 WARNING  d:\\BeamNGpy\\bng\\lib\\site-packages\\ipykernel\\ipkernel.py:283: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.\n",
      "  and should_run_async(code)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# setting up mod\n",
    "myModPath = beamng.user / 'mods' / 'genericResearchMod.zip' \n",
    "geCode = 'gameEngineCode.lua'\n",
    "zipGEpath = str(Path('lua') / 'ge' / 'extensions' / 'util' / geCode)\n",
    "veCode = 'vehicleEngineCode.lua'\n",
    "zipVEpath = str(Path('lua') / 'vehicle' / 'extensions' / veCode)\n",
    "localDir = Path(os.path.abspath('.'))\n",
    "with zipfile.ZipFile(str(myModPath), 'w') as ziph:\n",
    "    ziph.write(localDir / geCode, arcname=zipGEpath)\n",
    "    ziph.write(localDir / veCode, arcname=zipVEpath)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Testing the mod\n",
    "\n",
    "To test the mod we start BeamNG.tech and give the python BeamNG class the location of the gameengine mod within the \"genericResearchMod.zip/lua/ge/extensions/\" directory. This is necessary to register the file as its own lua module within the game.\n",
    "\n",
    "After registration, it is available as util_gameEngineCode within the game - try typing `dump(util_gameEngineCode)` in the game's command prompt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2021-01-05 09:35:50,948 INFO     Opening BeamNPy instance...\n",
      "2021-01-05 09:35:50,952 INFO     Started BeamNGpy server on localhost:64256\n",
      "2021-01-05 09:35:50,953 DEBUG    Starting BeamNG process: ['D:\\\\BeamNG\\\\game\\\\Bin64\\\\BeamNG.drive.x64.exe', '-console', '-rport', '64256', '-rhost', 'localhost', '-nosteam', '-physicsfps', '4000', '-lua', \"registerCoreModule('util/researchGE');registerCoreModule('util/gameEngineCode')\", '-userpath', 'C:\\\\Users\\\\Pascale\\\\Documents\\\\BeamNG.drive']\n",
      "2021-01-05 09:35:57,601 DEBUG    Connection established. Awaiting \"hello\"...\n",
      "2021-01-05 09:35:57,602 INFO     Started BeamNGpy communicating on ('127.0.0.1', 50700)\n",
      "2021-01-05 09:36:01,835 DEBUG    Starting vehicle server for V:ego_vehicle on: localhost:64257\n",
      "2021-01-05 09:36:04,200 DEBUG    Starting vehicle server for V:some_vehicle on: localhost:64258\n"
     ]
    }
   ],
   "source": [
    "bng = beamng.open(extensions=[\"util/gameEngineCode\"])\n",
    "bng.load_scenario(scenario)\n",
    "bng.start_scenario()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## How to Call Lua Module Functions\n",
    "\n",
    "BeamNG.tech and BeamNGpy communicate via sockets. \n",
    "Function parameters are send with the help of python dictionaries that are available as lua tables in the game. \n",
    "The mini mod from this tutorial follows the convention the BeamNGpy library established: Every value for the 'type' key helps identifying the appropriate handler, which for 'Foo' is expected to be 'handleFoo'.\n",
    "The checkmessage function in researchGE.lua and researchVE.lua checks whether a corresponding function is locally available and, if not, calls the `onSocketMessage` hook for every extension. \n",
    "\n",
    "## Using Acknowledgements\n",
    "\n",
    "In order to use the beamngcommon `ack` function decorator, the first function argument has to always be the object that also handles the socket. That is either the instance of the python BeamNG class or a vehicle object. \n",
    "\n",
    "## Data Transfer between BeamNGpy and BeamNG\n",
    "\n",
    "Any return value needs to be send with the help of the socket. See here an example on how to do it on the game engine side. The code for the vehicle side is the same, it just needs to be implemented in researchVE.lua.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "@ack('FooAck')\n",
    "def callFoo(bng):\n",
    "    \"\"\" Executes handleFoo defined in gameEngineCode.lua.\n",
    "    \"\"\"\n",
    "    data = dict(type='Foo', someName = 'YourName')\n",
    "    bng.send(data)\n",
    "\n",
    "@ack('BarAck')\n",
    "def callBar(veh):\n",
    "    \"\"\" Executes handlebar defined in VehicleEngineCode.lua.\n",
    "    Here the code is executed in the VM of the etk800.\n",
    "    \"\"\"\n",
    "    data = dict(type='Bar', text = 'lorem ipsum...' )\n",
    "    veh.send(data)\n",
    "\n",
    "def dataExchange(bng):\n",
    "    \"\"\"Demonstrates how to transfer in game data to the python side.\n",
    "    \"\"\"\n",
    "    data = dict(type='GetListOfVehicleModels')\n",
    "    bng.send(data)\n",
    "    response = bng.recv()\n",
    "    assert response['type'] == 'ListOfVehicleModels'\n",
    "    print('List of spawned vehicle models: ', response['data'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "callFoo(bng)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "callBar(etk)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "List of spawned vehicle models:  ['etk800', 'pickup']\n"
     ]
    }
   ],
   "source": [
    "dataExchange(bng)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Don't see anything?\n",
    "\n",
    "If you have trouble finding the messages in the log file or command prompt, search for 'gameEngineCode' or 'vehicleEngineCode'. These are the log tags of the respective modules."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2021-01-05 09:36:29,813 INFO     Closing BeamNGpy instance...\n",
      "2021-01-05 09:36:29,813 DEBUG    Killing BeamNG process...\n",
      "2021-01-05 09:36:29,897 WARNING  C:\\Python38-64\\lib\\subprocess.py:942: ResourceWarning: subprocess 7572 is still running\n",
      "  _warn(\"subprocess %s is still running\" % self.pid,\n",
      "\n"
     ]
    }
   ],
   "source": [
    "bng.close()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
